#ifndef __M_LOOP_H__
#define __M_LOOP_H__

#pragma once

#include "log.hpp"
#include "poller.hpp"
#include "channel.hpp"
#include "timerWheel.hpp"
#include <vector>
#include <thread>
#include <mutex>
#include <memory>
#include <functional>
#include <assert.h>
#include <sys/eventfd.h>

class EventLoop
{
using func = std::function<void()>;
private:
    std::thread::id _tid;
    Poller _poller;
    std::vector<func> _tasks;//任务池
    int _evfd;
    std::unique_ptr<Channel> _ev_channel;//设置定时任务 开启读事件监控这两个任务回调都需要通过触发evfd的读事件来执行
    std::mutex _mutex;
    TimerWheel _time_wheel;

private:
    void runAllTask()
    {
        std::vector<func> funcs;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _tasks.swap(funcs);
        }
        for (auto& f : funcs)
            f();
    }

    uint64_t createEventFd()
    {
        int evfd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);//禁止进程复制 | 非阻塞
        if (evfd < 0)
        {
            LOG(ERR, "create eventfd failed");
            exit(-1);
        }
        return evfd;
    }

    void readEventFd()
    {
        //LOG(DEG, "read evfd");
        uint64_t count = 0;
        int n = read(_evfd, &count, sizeof count);
        if (n < 0)
        {
            if (errno == EINTR || errno == EAGAIN)
                return;
            LOG(ERR, "read eventfd failed");
            exit(-1);
        }
    }

    /*
    调用runInLoop后 如果线程id与loop对象不匹配 任务会被添加到任务池
    为了避免poller上没有事件就绪触发runAllTask 导致任务池的任务无法被立即处理
    就添加了一个evfd 将任务添加到任务池后 会调用weakup向evfd写入数据 从而触发描述符读事件
    poller监控到evfd的读事件 调用读事件回调后 就会调用runAllTask执行任务池的任务
    */
    void weakUpEventFd()
    {
        //LOG(DEG, "weak up evfd");
        uint64_t val = 1;
        int n = write(_evfd, &val, sizeof val);
        if (n < 0)
        {
            if (errno == EINTR || errno == EAGAIN)
                return;
            LOG(ERR, "write eventfd failed");
            exit(-1);
        }
    }

public:
    EventLoop()
    : _tid(std::this_thread::get_id()),
      _evfd(createEventFd()),
      _ev_channel(new Channel(_evfd, this)),
      _time_wheel(this)
    {
        /*
        ev描述符的读事件监控是在创建EventLoop对象的那个线程中设置的 当主线程通过runInLoop接口压入任务后 会调用weakup向从线程ev写入数据
        从线程loop对象的poller监控到读事件 就会在处理完ev的读事件回调后 会执行loop任务池中的任务 
        */
        _ev_channel->setReadCallback(std::bind(&EventLoop::readEventFd, this));
        _ev_channel->monitorRead();
    }

    void start()
    {
        while (true)
        {
            //事件监控
            std::vector<Channel*> active;
            _poller.Wait(&active);
            //事件处理
            for (int i = 0; i < active.size(); i++)//如果持续不停的有事件触发 且事件处理时间不长 那么每轮事件处理的间隔会非常短 甚至远远小于1s
            {
                active[i]->handleEvent();
                //_tasks.push_back(std::bind(&Channel::handleEvent, active[i]));//TODO 这里要不要直接调用handleEvent 不放入任务池
            }
            //执行任务
            runAllTask();//1.执行之前EventLoop对象与当前线程id不匹配时 堆积在任务池的任务 2.统一在此执行连接的释放关闭操作
        }
    }

    bool isInLoop() { return std::this_thread::get_id() == _tid; }

    void assertInLoop() { assert(std::this_thread::get_id() == _tid); }

    void runInLoop(const func& cb)
    {
        if (isInLoop())
        {
            cb();
        }
        /*
        如果eventloop不属于当前线程 那么回调任务需要等待有连接触发事件后再和连接事件一起统一处理 或者通过触发evfd读事件来处理回调任务
        因为epoll模型属于对应的线程(loop) 如果描述符触发事件被epoll模型监控到 则会epoll模型会通知对应的线程对事件进行处理 因此对监控事件的处理一定是在当前线程内的 
        */
        else//当前执行流与当前loop对象不匹配 将任务压入任务池 等待对应的执行流触发事件后再对任务进行处理
        {
            pushTaskInLoop(cb);
        }
    }

    void pushTaskInLoop(const func& cb)
    {
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _tasks.push_back(cb);
        }
        weakUpEventFd();//没有连接事件就绪时 会阻塞对任务的处理 需要向evfd写入数据保证下一轮事件监控必定触发其可读事件 从而执行任务
    }

    std::thread::id getTid() { return _tid; }

    Channel* findChannel(int fd) { return _poller.findChannel(fd); } 

    void controlEpollEvent(Channel* channel, int optype) { _poller.controlEpollEvent(channel, optype); }

    void addTimerTask(uint64_t id, uint32_t timeout, const TimerTask::TaskFunc& task_cb) { _time_wheel.addTimerTask(id, timeout, task_cb); }

    void refreshTimerTask(uint64_t id) { _time_wheel.refreshTimerTask(id); }

    void cancelTimerTask(uint64_t id) { _time_wheel.cancelTimerTask(id); }

    bool hasTimerTask(uint64_t id) { return _time_wheel.hasTimerTask(id); }
};

void Channel::updateEventMonitor()
{ 
    if (_loop->findChannel(this->getFd()) == nullptr)//不在hash中 说明之前没有为当前连接设置过事件监控 不在epoll模型中
        //_loop->runInLoop(std::bind(&EventLoop::controlEpollEvent, _loop, this, EPOLL_CTL_ADD));//将连接添加到epoll模型中并设置相应事件监控
        _loop->controlEpollEvent(this, EPOLL_CTL_ADD);
    else
        _loop->controlEpollEvent(this, EPOLL_CTL_MOD);//连接已在epoll模型中 仅需设置相应的事件监控
}

void Channel::removeEventMonitor() {  _loop->controlEpollEvent(this, EPOLL_CTL_DEL); }

void TimerWheel::addTimerTask(uint64_t id, uint32_t timeout, const TimerTask::TaskFunc& task_cb)
{
    //每个TimerWheel定时器对应一个EventLoop 也就是一个TimerWheel只能同时被一个线程操作 无需加锁就能保证线程安全问题
    _loop->runInLoop(std::bind(&TimerWheel::addTimerTaskInLoop, this, id, timeout, task_cb));
}

void TimerWheel::refreshTimerTask(uint64_t id)
{
    _loop->runInLoop(std::bind(&TimerWheel::refreshTimerTaskInLoop, this, id));
}

void TimerWheel::cancelTimerTask(uint64_t id)
{
    _loop->runInLoop(std::bind(&TimerWheel::cancelTimerTaskInLoop, this, id));
}

bool TimerWheel::hasTimerTask(uint64_t id)
{
    auto it = _timer_tasks.find(id);
    return it != _timer_tasks.end();
}

#endif
